package com.gitee.sop.message.util.scheduler;


import org.quartz.*;

import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author: huwei
 * @date: 2020/3/30 15:04
 * @version: 1.0.0
 */
public class JobUtil {
    /**
     * 获取任务构建者
     *
     * @return
     */
    public static JobBuilder newBuilder() {
        return new JobBuilder();
    }

    /**
     * 获取当前任务的上下文；
     * 注意：该方法用在执行任务 或回调任务的方法体中，其它任何地方使用必然返回null
     *
     * @param jobName 工作名
     * @param group   工作组
     * @return
     */
    public static JobExecutionContext getContext(String jobName, String group) {
        return getContext(new JobKey(jobName, group));
    }

    /**
     * 获取当前任务的上下文；
     * 注意：该方法用在执行任务 或回调任务的方法体中，其它任何地方使用必然返回null
     *
     * @param jobKey 工作key
     * @return
     */
    public static JobExecutionContext getContext(JobKey jobKey) {
        JobBuilder.Task task = JobBuilder.JobTaskHolder.taskMap.get(jobKey);
        return task == null ? null : task.getInnerThreadObjectHolder().getContext();
    }

    /**
     * @author: huwei
     * @date: 2020/3/27 15:29
     * @version: 1.0.0
     */
    public static class JobBuilder {

        private JobKey jobKey;

        private Task task;

        private JobBuilder() {
            task = new Task();
        }

        public JobBuilder withKey(String jobName, String group) {
            return withKey(new JobKey(jobName, group));
        }

        public JobBuilder withKey(JobKey jobKey) {
            this.jobKey = jobKey;
            return this;
        }

        public JobBuilder withJob(NoCallExecuteFunction task) {
            this.task.setNoCallExecuteFunction(task);
            return this;
        }

        public <T> JobBuilder withJob(NoCallParameterExecuteFunction<T> task, T arg) {
            this.task.setNoCallParameterExecuteFunction(task).setArg(arg);
            return this;
        }

        public <R> JobBuilder withJob(ExecuteFunction<R> task, CallFunction<R> call) {
            this.task.setExecuteFunction(task).setCallFunction(call);
            return this;
        }

        public <T, R> JobBuilder withJob(ParameterExecuteFunction<T, R> task, T arg, CallFunction<R> call) {
            this.task.setParameterExecuteFunction(task).setArg(arg).setCallFunction(call);
            return this;
        }

        public JobDetail build() {
            if (Objects.isNull(this.jobKey)) {
                throw new RuntimeException("job key can not null");
            }
            if ((Objects.isNull(this.task.getParameterExecuteFunction()) || Objects.isNull(this.task.getArg()))
                    && Objects.isNull(this.task.getExecuteFunction())
                    && Objects.isNull(this.task.getNoCallExecuteFunction()) && Objects.isNull(this.task.getNoCallParameterExecuteFunction())) {
                throw new RuntimeException("execute job can not null");
            }

            JobDetail jobDetail = org.quartz.JobBuilder.newJob(CommonJob.class).withIdentity(this.jobKey).build();
            JobTaskHolder.taskMap.put(this.jobKey, this.task);
            return jobDetail;
        }

        /**
         * 通用的工作类 ,该类必须为公共静态内部类，这样才能够被Quartz反射成功
         */
        public static class CommonJob implements Job {
            @Override
            public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
                Task task = JobTaskHolder.taskMap.get(jobExecutionContext.getJobDetail().getKey());
                if (task == null) {
                    System.err.println("--- 没有" + jobExecutionContext.getJobDetail().getKey() + "所对应到工作");
                    return;
                }

                //任务前置任务
                before(jobExecutionContext, task);

                try {
                    //任务执行
                    Object res = null;
                    if (Objects.nonNull(task.getParameterExecuteFunction())) {
                        res = task.getParameterExecuteFunction().exe(task.getArg());
                    } else if (Objects.nonNull(task.getExecuteFunction())) {
                        res = task.getExecuteFunction().exe();
                    } else if (Objects.nonNull(task.getNoCallExecuteFunction())) {
                        task.getNoCallExecuteFunction().exe();
                    } else if (Objects.nonNull(task.getNoCallParameterExecuteFunction())) {
                        task.getNoCallParameterExecuteFunction().exe(task.getArg());
                    }

                    //有参回调
                    if (Objects.nonNull(res) && Objects.nonNull(task.getCallFunction())) {
                        task.getCallFunction().call(res);
                    }

                } catch (Throwable throwable) {
                    error(jobExecutionContext, task, throwable);
                }

                //任务完整之后执行
                after(jobExecutionContext, task);
            }

            private void before(JobExecutionContext jobExecutionContext, Task task) {
                //创建JobUtil对象，为用户提供工具处理
                task.setInnerThreadObjectHolder(new InnerThreadObjectHolder(jobExecutionContext));
            }

            private void after(JobExecutionContext jobExecutionContext, Task task) {
                try {
                    //清除job工具，释放资源
                    task.getInnerThreadObjectHolder().reset();
                    task.setInnerThreadObjectHolder(null);
                } catch (Exception e) {
                }
            }

            private void error(JobExecutionContext jobExecutionContext, Task task, Throwable throwable) {
                //异常任务
                throwable.printStackTrace();
                throw new RuntimeException("任务执行异常：".concat(throwable.getMessage()), throwable);
            }
        }

        private class Task<T, R> {
            NoCallExecuteFunction noCallExecuteFunction;
            NoCallParameterExecuteFunction<T> noCallParameterExecuteFunction;
            ParameterExecuteFunction<T, R> parameterExecuteFunction;
            ExecuteFunction<R> executeFunction;
            CallFunction<R> callFunction;
            T arg;

            InnerThreadObjectHolder innerThreadObjectHolder;

            public NoCallExecuteFunction getNoCallExecuteFunction() {
                return noCallExecuteFunction;
            }

            public Task setNoCallExecuteFunction(NoCallExecuteFunction noCallExecuteFunction) {
                this.noCallExecuteFunction = noCallExecuteFunction;
                return this;
            }

            public NoCallParameterExecuteFunction<T> getNoCallParameterExecuteFunction() {
                return noCallParameterExecuteFunction;
            }

            public Task setNoCallParameterExecuteFunction(NoCallParameterExecuteFunction<T> noCallParameterExecuteFunction) {
                this.noCallParameterExecuteFunction = noCallParameterExecuteFunction;
                return this;
            }

            public ParameterExecuteFunction<T, R> getParameterExecuteFunction() {
                return parameterExecuteFunction;
            }

            public Task setParameterExecuteFunction(ParameterExecuteFunction<T, R> parameterExecuteFunction) {
                this.parameterExecuteFunction = parameterExecuteFunction;
                return this;
            }

            public ExecuteFunction<R> getExecuteFunction() {
                return executeFunction;
            }

            public Task setExecuteFunction(ExecuteFunction<R> executeFunction) {
                this.executeFunction = executeFunction;
                return this;
            }

            public CallFunction<R> getCallFunction() {
                return callFunction;
            }

            public Task setCallFunction(CallFunction<R> callFunction) {
                this.callFunction = callFunction;
                return this;
            }

            public T getArg() {
                return arg;
            }

            public Task setArg(T arg) {
                this.arg = arg;
                return this;
            }

            public InnerThreadObjectHolder getInnerThreadObjectHolder() {
                return innerThreadObjectHolder;
            }

            public Task setInnerThreadObjectHolder(InnerThreadObjectHolder innerThreadObjectHolder) {
                this.innerThreadObjectHolder = innerThreadObjectHolder;
                return this;
            }
        }

        /**
         * 任务持有对象
         */
        private static final class JobTaskHolder {
            static Map<JobKey, Task> taskMap = new ConcurrentHashMap<>();
        }

        /**
         * 任务工具 ，用于获取原始Quartz方法执行到JobExecutionContext对象，以及MyJobDetail对象
         */
        public static class InnerThreadObjectHolder {
            /**
             * 任务执行时，原生JobExecutionContext对象
             */
            private ThreadLocal<JobExecutionContext> jobExecutionContextThreadLocal = new InheritableThreadLocal<>();

            private InnerThreadObjectHolder(JobExecutionContext jobExecutionContext) {
                this.jobExecutionContextThreadLocal.set(jobExecutionContext);
            }

            public JobExecutionContext getContext() {
                return jobExecutionContextThreadLocal.get();
            }

            private void setContext(JobExecutionContext context) {
                jobExecutionContextThreadLocal.set(context);
            }

            private void removeContext() {
                jobExecutionContextThreadLocal.remove();
            }

            private void reset() {
                //释放资源
                jobExecutionContextThreadLocal.remove();
            }
        }
    }
}
